package com.atguigu.lease.web.admin.service.impl;

import com.atguigu.lease.common.exception.StgyException;
import com.atguigu.lease.common.result.ResultCodeEnum;
import com.atguigu.lease.model.entity.*;
import com.atguigu.lease.web.admin.dto.RoomInfoSearchDto;
import com.atguigu.lease.web.admin.dto.RoomInfoSubmitDto;
import com.atguigu.lease.web.admin.mapper.RoomInfoMapper;
import com.atguigu.lease.web.admin.service.*;
import com.atguigu.lease.web.admin.vo.RoomInfoDetailVo;
import com.atguigu.lease.web.admin.vo.RoomInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.stream.Collectors;

/**
* @author 1
* @description 针对表【room_info(房间信息表)】的数据库操作Service实现
* @createDate 2024-08-28 11:00:26
*/
@Service
@Slf4j
public class RoomInfoServiceImpl extends ServiceImpl<RoomInfoMapper, RoomInfo>
    implements RoomInfoService{
    @Resource
    RoomFacilityService roomFacilityService;
    @Resource
    RoomAttrValueService roomAttrValueService;
    @Resource
    RoomPaymentTypeService roomPaymentTypeService;
    @Resource
    RoomLabelService roomLabelService;
    @Resource
    FileService fileService;
    @Resource
    RoomLeaseTermService roomLeaseTermService;
    @Resource
    GraphInfoService graphInfoService;
    /*
        当业务类中 A方法调用了B方法时，默认会隐式使用 this.B()
        该场景会导致 调用B方法的动态代理失效(面试题)
        解决： 通过自动装配从容器中装配业务类对象自己 来调用B方法
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdateRoomInfoSubmitDto(RoomInfoSubmitDto submitDto) {
        //1、数据唯一性校验： 同一个公寓下同一个房间号必须唯一
        long id = submitDto.getId()==null?-1L: submitDto.getId();
        // 查询是否存在重复的房间信息
        RoomInfo roomInfo = this.getOne(Wrappers.lambdaQuery(RoomInfo.class)
                .eq(RoomInfo::getApartmentId, submitDto.getApartmentId())
                .eq(RoomInfo::getRoomNumber, submitDto.getRoomNumber())
                .last("limit 1")
        );
        if(roomInfo!=null && !roomInfo.getId().equals(id)){
            throw new StgyException(ResultCodeEnum.DATA_EXISTS_ERROR);
        }
        List<String> graphUrls = null;
        //2、新增或更新
        //2.1 如果是更新 先删除房间映射的数据
        // 因为本次更新提交的映射数据 可能是表中已存在 也可能是新增的，也有表中存在 请求没有提交的
        if(id != -1l){
            graphUrls = deleteRoomExtra(id);
        }
        //2.2 新增房间 或 更新房间
        submitDto.setIsRelease(1);//数据库没有默认值
        this.saveOrUpdate(submitDto);
        //    保存房间映射的数据
        // 获取新增后的id
        id = submitDto.getId();
        long finalId = id;
        if(!CollectionUtils.isEmpty(submitDto.getFacilityInfoIds())){
            List<RoomFacility> roomFacilities = submitDto.getFacilityInfoIds().stream().map(roomFacilityId -> {
                RoomFacility roomFacility = new RoomFacility();
                roomFacility.setFacilityId(roomFacilityId);
                roomFacility.setRoomId(finalId);
                return roomFacility;
            }).collect(Collectors.toList());
            roomFacilityService.saveBatch(roomFacilities);
        }
        if(!CollectionUtils.isEmpty(submitDto.getLabelInfoIds())){
            List<RoomLabel> roomLabels = submitDto.getLabelInfoIds().stream().map(labelId -> {
                RoomLabel roomLabel = new RoomLabel();
                roomLabel.setLabelId(labelId);
                roomLabel.setRoomId(finalId);
                return roomLabel;
            }).collect(Collectors.toList());
            roomLabelService.saveBatch(roomLabels);
        }
        if(!CollectionUtils.isEmpty(submitDto.getAttrValueIds())){
            List<RoomAttrValue> roomAttrValues = submitDto.getAttrValueIds().stream().map(avId -> {
                RoomAttrValue roomAttrValue = new RoomAttrValue();
                roomAttrValue.setAttrValueId(avId);
                roomAttrValue.setRoomId(finalId);
                return roomAttrValue;
            }).collect(Collectors.toList());
            roomAttrValueService.saveBatch(roomAttrValues);
        }

        if(!CollectionUtils.isEmpty(submitDto.getLeaseTermIds())){
            List<RoomLeaseTerm> roomLeaseTerms = submitDto.getLeaseTermIds().stream().map(ltId -> {
                RoomLeaseTerm roomLeaseTerm = new RoomLeaseTerm();
                roomLeaseTerm.setLeaseTermId(ltId);
                roomLeaseTerm.setRoomId(finalId);
                return roomLeaseTerm;
            }).collect(Collectors.toList());
            roomLeaseTermService.saveBatch(roomLeaseTerms);
        }
        if(!CollectionUtils.isEmpty(submitDto.getPaymentTypeIds())){
            List<RoomPaymentType> roomPaymentTypes = submitDto.getPaymentTypeIds().stream().map(fId -> {
                RoomPaymentType roomPaymentType = new RoomPaymentType();
                roomPaymentType.setPaymentTypeId(fId);
                roomPaymentType.setRoomId(finalId);
                return roomPaymentType;
            }).collect(Collectors.toList());
            roomPaymentTypeService.saveBatch(roomPaymentTypes);
        }
        if(!CollectionUtils.isEmpty(submitDto.getGraphVoList())){

            for (GraphInfo graphInfo : submitDto.getGraphVoList()) {
                graphInfo.setId(null);
                graphInfo.setItemId(finalId);
                graphInfo.setItemType(2);//房间
            }
            graphInfoService.saveBatch(submitDto.getGraphVoList());
        }
        if(!CollectionUtils.isEmpty(graphUrls)){
            //批量删除房间的图片列表：  1、删除图片的异常被处理了，不会影响主业务、2、和主业务并行执行(子线程异步执行)
            //整合springtask： spring-context包提供了异步框架  主要通过简单的注解就可以实现异步操作
            // 1、启动类启用异步编程  2、需要异步的方法上使用异步注解
            fileService.batchDelete(graphUrls,id);
        }


        return true;
    }

    @NotNull
    private List<String> deleteRoomExtra(long id) {
        List<String> graphUrls;
        //room_attr_value 根据房间id删除它所有的 属性值
        //逻辑删除：  以后新增或者更新时，如果表设置了 room_id映射attr_value_id的组合唯一索引，数据库约束会报错
        roomAttrValueService.remove(Wrappers.lambdaQuery(RoomAttrValue.class)
                .eq(RoomAttrValue::getRoomId , id)
        );
        //room_facility
        roomFacilityService.remove(Wrappers.lambdaQuery(RoomFacility.class)
                .eq(RoomFacility::getRoomId , id)
        );
        //room_label
        roomLabelService.remove(Wrappers.lambdaQuery(RoomLabel.class)
                .eq(RoomLabel::getRoomId , id)
        );
        //room_payment_type
        roomPaymentTypeService.remove(Wrappers.lambdaQuery(RoomPaymentType.class)
                .eq(RoomPaymentType::getRoomId , id)
        );
        //room_lease_term
        roomLeaseTermService.remove(Wrappers.lambdaQuery(RoomLeaseTerm.class)
                .eq(RoomLeaseTerm::getRoomId , id)
        );
        //graph_info
        //在删除前获取房间的图片列表
        List<GraphInfo> graphInfos = graphInfoService.list(Wrappers.lambdaQuery(GraphInfo.class)
                .eq(GraphInfo::getItemId, id)
                .eq(GraphInfo::getItemType , 2));
        graphUrls = graphInfos.stream().map(graphInfo -> graphInfo.getUrl())
                .collect(Collectors.toList());
        //文件删除： 删除minio的冗余数据  ， 减少磁盘压力
//            graphInfoService.remove(Wrappers.lambdaQuery(GraphInfo.class)
//                    .eq(GraphInfo::getItemId , id)
//            );
        //根据房间id查询到的一组图片的 id集合 批量删除
        graphInfoService.removeBatchByIds(graphInfos.stream().map(graphInfo -> graphInfo.getId())
                .collect(Collectors.toList()));
        return graphUrls;
    }

    @Override
    public Page<RoomInfoVo> pageItem(RoomInfoSearchDto searchDto) {
        //创建分页对象：封装分页条件
        Page<RoomInfoVo> page = new Page<>(searchDto.getCurrent() , searchDto.getSize());
        //自定义sql：使用page对象携带分页条件、使用queryWrapper携带过滤条件
        QueryWrapper<RoomInfoVo> queryWrapper = Wrappers.query();
        Long provinceId = searchDto.getProvinceId();
        if(provinceId != null){
            //生成的条件sql为： t3.province_id = 1
            queryWrapper.eq("t3.province_id",provinceId);
        }
        Long cityId = searchDto.getCityId();
        if(cityId != null){
            queryWrapper.eq("t3.city_id",cityId);
        }
        Long districtId = searchDto.getDistrictId();
        if(districtId != null){
            queryWrapper.eq("t3.district_id",districtId);
        }
        Long apartmentId = searchDto.getApartmentId();
        if(apartmentId != null){
             queryWrapper.eq("t1.apartment_id", apartmentId);
        }
        queryWrapper.eq("t1.is_deleted" , 0 );
        List<RoomInfoVo> roomInfoVos =  baseMapper.selectPageItem(page,queryWrapper);
        page.setRecords(roomInfoVos);
        return page;
    }
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = "stgy:admin:room" , key = "#id" )
    @Override
    public boolean removeRoomInfoById(Long id) {
        this.removeById(id);
        //删除额外的数据 返回图片地址列表
        List<String> graphUrls = this.deleteRoomExtra(id);
        //删除图片文件
        fileService.batchDelete(graphUrls , id);
        return true;
    }

    @Override
    // stgy:admin:room:10001213213
    @Cacheable(value = "stgy:admin:room" , key = "#id" )//index(写固定字符串)和key(支持springEL表达式 可以动态获取参数)用来组合 缓存的键
    public RoomInfoDetailVo getDetailById(Long id) {
        /*
            如果业务是用户的业务：
                一般分步查询
                    - 可以结合异步编排CompletableFuture 可以将多个数据库操作编排为串行+并行的方式执行
                        创建全局变量：RoomInfoDetailVo
                        子线程1、查询room详情 将room信息设置给RoomInfoDetailVo
                        子线程2、查询房间的标签列表  将标签列表信息设置给RoomInfoDetailVo
                        子线程3、查询房间的配套列表
                        子线程4、查询房间的图片列表
               一个大sql查询：
                        多表联查sql执行时，之间的联查效率低

              如果一个业务方法会在多个地方使用 建议抽取业务方法
              > spring-context:
                1、异步任务
                    启动类：@EnableAsync 开启支持异步任务
                    再需要异步执行的业务方法上：@Async 使用异步任务
                2、自动缓存管理：
                    2.1 配置类上： @EnableCaching 开启自动缓存管理
                    2.2 再需要缓存的业务方法(查询方法)上： @Cacheable注解 自动缓存管理
                            - 方法被调用时，先检查该方法是否有缓存，如果有直接返回
                            - 如果没有，调用方法，并将执行的结果缓存
                            - 返回结果

                            @Cacheable(value = "stgy:admin:room" , key = "#id" )
                            拼接缓存的键为：    stgy:admin:room::id值
                        @CachingPut:
                                - 新增或更新时，将方法的返回结果缓存
                        @CachingEvict:
                                - 新增、更新或删除时，将指定的缓存删除
                    2.3 为springcache提供缓存管理器
                            spring提供了缓存管理器的接口：约束API  CacheManager
                                - 其他的缓存如果希望支持springcache，可以实现缓存管理器的接口
                                - 将缓存管理器对象注入到容器中以后，自动缓存管理可以调用它对应的方法管理缓存
                        1、我们希望使用redis管理缓存
                            需要引入redis的启动器
                            redis配置
                        2、将springdataredis启动器实现了CacheManager的类的对象创建配置好注入到容器中

         */

        return baseMapper.selectDetailById(id);
    }

    @Override
    public List<RoomInfo> listRoomInfoByApartmentId(Long apartmentId) {

                /*
        roomInfoService.list(Wrappers.lambdaQuery(RoomInfo.class)
                .eq(RoomInfo::getApartmentId , apartmentId)
                .eq(RoomInfo::getIsRelease , 1))
         */

        return baseMapper.selectRoomInfosByApartmentId(apartmentId);
    }
}




